package com.oracle.coherence.patterns.security.management;

import com.oracle.coherence.patterns.security.KrbTicketPrincipal;
import com.oracle.coherence.patterns.security.accesscontrol.FilePermissionsChecker;
import com.oracle.coherence.patterns.security.accesscontrol.PermissionsChecker;
import com.tangosol.net.CacheFactory;

import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXPrincipal;
import javax.security.auth.Subject;
import java.security.Permission;
import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * An implementation of a {@link JMXAuthenticator} that
 * is used to authenticate a {@link com.oracle.coherence.patterns.security.KrbTicketPrincipal}.
 * <p/>
 * The authorisation is performed using a permissions file. The file name defaults
 * to jmx-permissions.xml unless this is overridden using the jmx.permissions JVM
 * system property.
 * <p/>
 *
 * @author Jonathan Knight
 */
public class BaseJMXAuthenticator implements JMXAuthenticator {
    public static final String JMX_PERMISSIONS = "jmx.permissions";
    public static final String JMX_PERMISSIONS_FILE = "jmx-permissions.xml";

    private PermissionsChecker permissionsChecker;

    /**
     * Default constructor.
     * <p/>
     * The constructor creates a new {@link FilePermissionsChecker}
     * and sets the permissions file name to jmx-permissions.xml
     * unless this is overridden with the -Djmx.permissions JVM
     * property.
     * <p/>
     */
    public BaseJMXAuthenticator() {
        String permissionsFile = System.getProperty(JMX_PERMISSIONS, JMX_PERMISSIONS_FILE);
        permissionsChecker = new FilePermissionsChecker(permissionsFile, "false");
    }

    /**
     * Return the {@link PermissionsChecker} that this access controller will
     * use to authorise access to JMX resources.
     * <p/>
     *
     * @return the {@link PermissionsChecker} that this access controller will
     *         use to authorise access to JMX resources.
     */
    PermissionsChecker getPermissionsChecker() {
        return permissionsChecker;
    }

    /**
     * Set the {@link PermissionsChecker} that this access controller will
     * use to authorise access to JMX resources.
     * <p/>
     *
     * @param permissionsChecker - the {@link PermissionsChecker} to use.
     */
    void setPermissionsChecker(PermissionsChecker permissionsChecker) {
        this.permissionsChecker = permissionsChecker;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings({"unchecked"})
    public Subject authenticate(Object credentials) {

        Set principals = new HashSet();

        if (credentials instanceof KrbTicketPrincipal) {
            KrbTicketPrincipal principal = (KrbTicketPrincipal) credentials;
            principals.add(principal);
            principals.add(new JMXPrincipal(principal.getName()));
            CacheFactory.log("Connection from " + principal.getName(), CacheFactory.LOG_INFO);
        } else if (credentials instanceof Subject) {
            principals = ((Subject)credentials).getPrincipals(KrbTicketPrincipal.class);
        } else {
            throw new SecurityException("You are not authorised to connect to this JMX server");
        }

        Subject subject = new Subject(true,
                principals,
                Collections.EMPTY_SET,
                Collections.EMPTY_SET);

        Permission permission = new JmxPermission("*", JmxAction.CONNECT);
        permissionsChecker.checkPermission(permission, subject);
        return subject;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {
        return (o != null && o instanceof BaseJMXAuthenticator);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return BaseJMXAuthenticator.class.hashCode();
    }

}
